var EAOverride = true;
var AErecorderNode;
var GCOverride = true;
var outputID = -1;
var EAoutputID = -1;
var queueExclusive = false;
var queueChromecast = false;
var selectedGC;
var MVsource;
var windowAudioNode;
const workerOptions = {
  OggOpusEncoderWasmPath: 'https://cdn.jsdelivr.net/npm/opus-media-recorder@latest/OggOpusEncoder.wasm',
  WebMOpusEncoderWasmPath: 'https://cdn.jsdelivr.net/npm/opus-media-recorder@latest/WebMOpusEncoder.wasm'
};
var recorder;

window.MediaRecorder = OpusMediaRecorder;

var audioWorklet = `class RecorderWorkletProcessor extends AudioWorkletProcessor {
    static get parameterDescriptors() {
      return [{
        name: 'isRecording',
        defaultValue: 0
      },
      {
        name: 'numberOfChannels',
        defaultValue: 2
      }
    ];
    }
  
    constructor() {
      super();
      this._bufferSize = 4096;
      this._buffers = null;
      this._initBuffer();
    }

    _initBuffers(numberOfChannels) {
      this._buffers = [];
      for (let channel=0; channel < numberOfChannels; channel++) {
        this._buffers.push(new Float32Array(this._bufferSize));
      }
    }
  
    _initBuffer() {
      this._bytesWritten = 0;
    }
  
    _isBufferEmpty() {
      return this._bytesWritten === 0;
    }
  
    _isBufferFull() {
      return this._bytesWritten === this._bufferSize;
    }


    _pushToBuffers(audioRawData, numberOfChannels) {
      if (this._isBufferFull()) {
          this._flush();
      }

      let dataLength = audioRawData[0].length;

      for (let idx=0; idx<dataLength; idx++) {
        for (let channel=0; channel < numberOfChannels; channel++) {
          let value = audioRawData[channel][idx];
          this._buffers[channel][this._bytesWritten] = value;
        }
        this._bytesWritten += 1;
      }
    }
  
    _flush() {
      let buffers = [];
      this._buffers.forEach((buffer, channel) => {
        if (this._bytesWritten < this._bufferSize) {
          buffer = buffer.slice(0, this._bytesWritten);
        }
        buffers[channel] = buffer;
      });
      this.port.postMessage({
        eventType: 'data',
        audioBuffer: buffers,
        bufferSize: this._bufferSize
      });
      this._initBuffer();
    }
  
    _recordingStopped() {
      this.port.postMessage({
        eventType: 'stop'
      });
    }
  
    process(inputs, outputs, parameters) {
      const isRecordingValues = parameters.isRecording;
      const numberOfChannels = parameters.numberOfChannels[0];   
      if (this._buffers === null) {
        this._initBuffers(numberOfChannels);
      }
      
      for (let dataIndex = 0; dataIndex < isRecordingValues.length; dataIndex++) 
      {
        const shouldRecord = isRecordingValues[dataIndex] === 1;
        if (!shouldRecord && !this._isBufferEmpty()) {
          this._flush();
          this._recordingStopped();
        }
  
        if (shouldRecord) {
          let audioRawData = inputs[0];
          this._pushToBuffers(audioRawData, numberOfChannels);
        }
      }
      return true;
    }
  
  }
  
  registerProcessor('recorder-worklet', RecorderWorkletProcessor);`;
var GCstream;
var searchInt;
var AMEx = {
  context: new AudioContext(),
  result: {},
  filter: [],
  EQRanges: [{
    f: 32,
    type: 'lowshelf'
  }, {
    f: 64,
    type: 'peaking'
  }, {
    f: 125,
    type: 'peaking'
  }, {
    f: 250,
    type: 'peaking'
  }, {
    f: 500,
    type: 'peaking'
  }, {
    f: 1000,
    type: 'peaking'
  }, {
    f: 2000,
    type: 'peaking'
  }, {
    f: 4000,
    type: 'peaking'
  }, {
    f: 8000,
    type: 'peaking'
  }, {
    f: 16000,
    type: 'highshelf'
  }]
};
var bassFilter;
var trebleFilter;

var AudioOutputs = {
  fInit: false,
  eqReady: false,
  activeCasts: [],
  castUI() {
    AMJavaScript.getRequest("ameres://html/cast_device.html", (content) => {
      var vm = new Vue({
        data: {
          devices: {
            cast: [],
            airplay: []
          },
          scanning: false,
          activeCasts: AudioOutputs.activeCasts
        },
        methods: {
          scan() {
            let self = this;
            this.scanning = true;
            AudioOutputs.getGCDevices();
            setTimeout(() => {
              self.devices.cast = ipcRenderer.sendSync("getKnownCastDevices");
              self.scanning = false;
            }, 2000);
            console.log(this.devices);
            vm.$forceUpdate();
          },
          setCast(device) {
            console.log(`requesting: ${device}`);
            AudioOutputs.playGC(device);
          },
          stopCasting() {
            AudioOutputs.stopGC();
            this.activeCasts = AudioOutputs.activeCasts;
            vm.$forceUpdate();
          },
          close() {
            modal.close();
          }
        }
      });
      var modal = new AMEModal({
        content: content,
        CloseButton: false,
        Style: {
          maxWidth: "600px"
        },
        OnCreate() {
          vm.$mount("#castdevices-vue");
          vm.scan();
        },
        OnClose() {
          _vues.destroy(vm);
        }
      });
    })
  },
  init: function (cb = function () { }) {
    AudioOutputs.fInit = true;
    searchInt = setInterval(function () {
      if (document.getElementById("apple-music-player")) {
        AudioOutputs.eqReady = true;
        document.getElementById("apple-music-player").crossOrigin = "anonymous";
        AudioOutputs.amplifyMedia(document.getElementById("apple-music-player"), 0);
        var context = AMEx.context;
        var source = AMEx.result.source;
        bassFilter = context.createBiquadFilter();
        bassFilter.type = "lowshelf";
        bassFilter.frequency.value = 200;
        bassFilter.gain.value = 0;

        trebleFilter = context.createBiquadFilter();
        trebleFilter.type = "highshelf";
        trebleFilter.frequency.value = 2000;
        trebleFilter.gain.value = 0;

        source.connect(bassFilter);
        bassFilter.connect(trebleFilter);
        trebleFilter.connect(context.destination);
        console.log("Attached EQ");

        if (queueExclusive) {
          console.log('we good');
          AudioOutputs.startExclusiveAudio(outputID);
        }

        cb();
        clearInterval(searchInt);
      }
    }, 1000);
    waitFor(() => {
      return queueChromecast &&
        ((document.getElementById("apple-music-player") != null &&
          document.getElementById("apple-music-player").readyState == 4) || (
            document.querySelector('apple-music-video-player') &&
            document.querySelector('apple-music-video-player').shadowRoot &&
            document.querySelector('apple-music-video-player').shadowRoot.querySelector('amp-video-player-internal') &&
            document.querySelector('apple-music-video-player').shadowRoot.querySelector('amp-video-player-internal').shadowRoot &&
            document.querySelector('apple-music-video-player').shadowRoot.querySelector('amp-video-player-internal').shadowRoot.querySelector('amp-video-player') &&
            document.querySelector('apple-music-video-player').shadowRoot.querySelector('amp-video-player-internal').shadowRoot.querySelector('amp-video-player').shadowRoot &&
            document.querySelector('apple-music-video-player').shadowRoot.querySelector('amp-video-player-internal').shadowRoot.querySelector('amp-video-player').shadowRoot.getElementById('apple-music-video-player') &&
            document.querySelector('apple-music-video-player').shadowRoot.querySelector('amp-video-player-internal').shadowRoot.querySelector('amp-video-player').shadowRoot.getElementById('apple-music-video-player').readyState == 4));
    }, () => AudioOutputs.playGC(selectedGC))

  },
  amplifyMedia: function (mediaElem, multiplier) {
    AMEx.context = new (window.AudioContext || window.webkitAudioContext),
      AMEx.result = {
        context: AMEx.context,
        source: AMEx.context.createMediaElementSource(mediaElem),
        gain: AMEx.context.createGain(),
        media: mediaElem,
        amplify: function (multiplier) {
          AMEx.result.gain.gain.value = multiplier;
        },
        getAmpLevel: function () {
          return AMEx.result.gain.gain.value;
        }
      };
    AMEx.result.source.connect(AMEx.result.gain);
    AMEx.result.gain.connect(AMEx.context.destination);
    AMEx.result.amplify(multiplier);
    return AMEx.result;
  },
  popup_generic: function ({
    title = "",
    content = document.createElement("div"),
    closefn = function () { },
    transparentBg = false,
    windowStyle = {},
    backdropStyle = {}
  }) {
    let backdrop = document.createElement("div");
    backdrop.style.width = "100%";
    backdrop.style.height = "100%";
    backdrop.style.position = "fixed";
    backdrop.style.top = 0;
    backdrop.style.left = 0;
    if (!transparentBg) {
      backdrop.style.background = "rgba(0,0,0,0.5)";
    } else {
      backdrop.style.background = "rgba(0,0,0,0.0)";
    };
    backdrop.style.zIndex = 10000;
    backdrop.style.display = "flex";
    backdrop.style.alignItems = "center";
    backdrop.style.justifyContent = "center";
    let win = document.createElement("div");
    win.style.width = "300px";
    win.style.background = "var(--modalBGColor)";
    win.style.zIndex = 10000;
    win.style.padding = "16px";
    win.style.borderRadius = "10px";
    Object.assign(backdrop.style, backdropStyle);
    Object.assign(win.style, windowStyle);
    let closeBtn = document.createElement("button");
    closeBtn.style.background = "var(--primaryColor)";
    closeBtn.style.borderRadius = "4px";
    closeBtn.style.padding = "8px 0px 8px 0px";
    closeBtn.style.width = "100%";
    closeBtn.style.fontWeight = "bold";
    closeBtn.style.margin = "12px 0px 0px 0px";
    closeBtn.innerHTML = "Close";
    closeBtn.id = "eq-close";
    closeBtn.addEventListener("click", function () {
      backdrop.remove();
      closefn();
    });
    let titleText = document.createElement("div");
    titleText.innerHTML = (title);
    titleText.style.fontWeight = "bold";


    win.appendChild(titleText);
    win.appendChild(content);
    win.appendChild(closeBtn);

    backdrop.appendChild(win);
    document.body.appendChild(backdrop);
  },
  ShowEQ: function () {
    if (!AudioOutputs.eqReady) {
      alert("Audio is not ready, Play a song to use this function.");
    };
    let backdrop = document.createElement("div");
    backdrop.style.width = "100%";
    backdrop.style.height = "100%";
    backdrop.style.position = "fixed";
    backdrop.style.top = 0;
    backdrop.style.left = 0;
    backdrop.style.background = "rgba(0,0,0,0.5)";
    backdrop.style.zIndex = 9999;
    backdrop.style.display = "flex";
    backdrop.style.alignItems = "center";
    backdrop.style.justifyContent = "center";
    backdrop.style.backdropFilter = "blur(12px) saturate(180%)";

    let win = document.createElement("div");
    win.style.width = "300px";
    win.style.background = "var(--modalBGColor)";
    win.style.zIndex = 10000;
    win.style.padding = "16px";
    win.style.borderRadius = "10px";


    let closeBtn = document.createElement("button");
    closeBtn.style.background = "var(--primaryColor)";
    closeBtn.style.borderRadius = "4px";
    closeBtn.style.padding = "8px 0px 8px 0px";
    closeBtn.style.width = "100%";
    closeBtn.style.fontWeight = "bold";
    closeBtn.style.margin = "12px 0px 0px 0px";

    closeBtn.innerHTML = "Close";
    closeBtn.addEventListener("click", function () {
      backdrop.remove()
    });

    let titleText = document.createElement("div");
    let bassText = document.createElement("div");
    let trebleText = document.createElement("div");
    let gainText = document.createElement("div");
    titleText.id = 'eq-menu';
    titleText.innerHTML = (`Equalizer`);
    titleText.style.fontWeight = "bold";
    bassText.innerHTML = (`Bass (${bassFilter.gain.value})`);
    trebleText.innerHTML = (`Treble (${trebleFilter.gain.value})`);
    gainText.innerHTML = (`Gain (${AMEx.result.gain.gain.value})`);


    let bassAdjust = document.createElement("input");
    bassAdjust.style.width = "100%";
    bassAdjust.type = "range";
    bassAdjust.min = -10;
    bassAdjust.max = 10;
    bassAdjust.value = bassFilter.gain.value;
    bassAdjust.addEventListener("input", function () {
      bassFilter.gain.value = this.value;
      bassText.innerHTML = `Bass (${bassFilter.gain.value})`;
    });

    let trebleAdjust = document.createElement("input");
    trebleAdjust.style.width = "100%";
    trebleAdjust.min = -10;
    trebleAdjust.max = 10;
    trebleAdjust.type = "range";
    trebleAdjust.value = trebleFilter.gain.value;
    trebleAdjust.addEventListener("input", function () {
      trebleFilter.gain.value = this.value;
      trebleText.innerHTML = `Treble (${trebleFilter.gain.value})`;
    });

    let gainAdjust = document.createElement("input");
    gainAdjust.style.width = "100%";
    gainAdjust.min = -1;
    gainAdjust.max = 1;
    gainAdjust.type = "range";
    gainAdjust.value = AMEx.result.gain.gain.value;
    gainAdjust.addEventListener("input", function () {
      AMEx.result.gain.gain.value = this.value;
      gainText.innerHTML = `Gain (${AMEx.result.gain.gain.value})`;
    });

    let bassLabel = document.createElement("label");
    let trebleLabel = document.createElement("label");
    let gainLabel = document.createElement("label");

    bassLabel.appendChild(bassText);
    trebleLabel.appendChild(trebleText);
    gainLabel.appendChild(gainText);

    bassLabel.appendChild(bassAdjust);
    bassLabel.appendChild(document.createElement("br"));
    trebleLabel.appendChild(trebleAdjust);
    trebleLabel.appendChild(document.createElement("br"));
    gainLabel.appendChild(gainAdjust);

    win.appendChild(titleText);
    win.appendChild(bassLabel);
    win.appendChild(trebleLabel);
    win.appendChild(gainLabel);
    win.appendChild(closeBtn);

    backdrop.appendChild(win);
    document.body.appendChild(backdrop);
  },
  getAudioDevices: function () {
    ipcRenderer.send('getAudioDevices', '');
  },
  startExclusiveAudio: async function (id) {

    if (AMEx.result.source != null || MVsource != null) {
      if (EAoutputID != id) {
        EAoutputID = id;
        EAOverride = false;
        ipcRenderer.send('muteAudio', true);
        ipcRenderer.send('enableExclusiveAudio', id);
        let blob = new Blob([audioWorklet], { type: 'application/javascript' });
        if (!AErecorderNode) {
          await AMEx.context.audioWorklet.addModule(URL.createObjectURL(blob))
            .then(() => {

              const channels = 2;
              AErecorderNode = new window.AudioWorkletNode(AMEx.context,
                'recorder-worklet',
                { parameterData: { numberOfChannels: channels } });
              AErecorderNode.port.onmessage = (e) => {
                const data = e.data;
                switch (data.eventType) {
                  case "data":
                    if (!EAOverride || !GCOverride) {
                      const audioData = data.audioBuffer;
                      const bufferSize = data.bufferSize;
                      if (!EAOverride) {
                        ipcRenderer.send('writePCM', audioData[0], audioData[1], bufferSize);
                      }
                    }
                    break;
                  case "stop":
                    break;
                }
              }



            });
        }

        AErecorderNode.parameters.get('isRecording').setValueAtTime(1, AMEx.context.currentTime);
        windowAudioNode = AMEx.context.createGain();
        AMEx.result.source.connect(windowAudioNode);
        windowAudioNode.connect(AErecorderNode);
      } else { console.log('device already in exclusive mode'); }
    } else {
      outputID = id;
      queueExclusive = true;
    }
  },
  stopExclusiveAudio: function () {
    try {
      AErecorderNode.parameters.get('isRecording').setValueAtTime(0, AMEx.context.currentTime);
    } catch (e) { }
    EAOverride = true;
    EAoutputID = -1;
    outputID = -1;
    queueExclusive = false;
    ipcRenderer.send('muteAudio', false);
    ipcRenderer.send('disableExclusiveAudio', '');
  },
  getGCDevices: function () {
    ipcRenderer.send('getChromeCastDevices', '');
  },
  playGC: async function (device) {
    console.log('wot');
    AudioOutputs.activeCasts.push(device);
    GCOverride = false;
    if (AMEx.result.source != null || MVsource != null) {
      queueChromecast = false;
      const musicType = (MusicKit.getInstance().nowPlayingItem != null) ? MusicKit.getInstance().nowPlayingItem["type"] ?? '' : '';
      const trackName = ((MusicKit.getInstance().nowPlayingItem != null) ? MusicKit.getInstance().nowPlayingItem.title ?? '' : '');
      const artistName = ((MusicKit.getInstance().nowPlayingItem != null) ? MusicKit.getInstance().nowPlayingItem.artistName ?? '' : '');
      const albumName = ((MusicKit.getInstance().nowPlayingItem != null) ? MusicKit.getInstance().nowPlayingItem.albumName ?? '' : '');
      ipcRenderer.send('performGCCast', device, "Apple Music Electron", "Playing ...", "3.0.0", '');
      windowAudioNode = AMEx.context.createGain();
      try {
        AMEx.result.source.connect(windowAudioNode);
      }
      catch (e) { }

      var options = {
        mimeType: 'audio/wav'
      };
      var destnode = AMEx.context.createMediaStreamDestination();
      windowAudioNode.connect(destnode);
      if (!recorder) {
        recorder = new MediaRecorder(destnode.stream, options, workerOptions);
        recorder.start(1);

        recorder.ondataavailable = function (e) {
          e.data.arrayBuffer().then(buffer => {
            if (!GCOverride) {
              ipcRenderer.send('writeWAV', buffer, preferences.audio.castingBitDepth);
            }
          }
          );
        }
      }

    } else { queueChromecast = true; selectedGC = device }


  },
  stopGC: function () {
    queueChromecast = false;
    try {
      recorder.stop();
      recorder = null;
    } catch (e) { }
    GCOverride = true;
    this.activeCasts = [];
    ipcRenderer.send('stopGCast', '');
  }
};



document.addEventListener('keydown', function (event) {
  if (event.ctrlKey || event.metaKey) {
    switch (String.fromCharCode(event.which).toLowerCase()) {
      case "2":
        if (document.getElementById('eq-menu')) {
          document.getElementById('eq-menu').parentNode.getElementsByTagName('button')[0].click();
        }
        else { AudioOutputs.ShowEQ(); }
        break;
      case "3":
        (EAOverride) ? (EAOverride = false) : (EAOverride = true);
        break;
    }
  }
});

function waitFor(condition, callback) {
  if (condition() == null || !condition()) {
    window.setTimeout(waitFor.bind(null, condition, callback), 1000);
  } else {
    callback();
  }
}

function setIntervalX(callback, delay, repetitions) {
  var x = 0;
  var intervalID = window.setInterval(function () {

    callback();

    if (++x === repetitions) {
      window.clearInterval(intervalID);
    }
  }, delay);
}

AudioOutputs.init()